/*!
 * \file quadruple.cpp
 */ 
#include <cassert>
#include <vector>
#include "tokens.h"
#include "utility.h"
#include "quadruple.h"
#include "scope.h"

static std::vector<CQuadruple> quadruples;

int pushQuadruple(const CQuadruple& q)
{
  quadruples.push_back(q);
  return quadruples.size()-1;
}

void popQuadruple()
{
  assert(!quadruples.empty());
  quadruples.pop_back();
}

const CQuadruple& getQuadruple(int i)
{
  assert(i>=0 && i<quadruples.size());
  return quadruples[i];
}

int getQuadrupleCount()
{
  return quadruples.size();
}

int quadrupleDump()
{
  for(size_t i=0;i<getQuadrupleCount();++i)
    getQuadruple(i).print();
  return 0;
}


//////////////////////////////////////////////////////////////////////////////


void COperand::print(bool showVoid) const
{
  static const char * const _class[]=
  {
    "VOID",
    "VALUE",
    "LABEL",
  };

  static const char * const _type[]=
  {
    "BOOL",
    "INTEGER",
    "FLOAT",
    "VECTOR",
  };

  static const char * const _flavor[]=
  {
    "CONST",
    "RVALUE",
    "LVALUE",
  };

  static_assert(COUNT(_class)==MAX_CLASS);
  static_assert(COUNT(_type)==Lang::MAX_TYPE);
  static_assert(COUNT(_flavor)==MAX_FLAVOR);

  if(m_class==VOID && !showVoid)
    return;

  printf("%s",_class[m_class]);
  if(m_class==VALUE)
  {
    printf(":%s:%s",_type[m_type],_flavor[m_flavor]);
    if(m_flavor==CONST)
    {
      switch(m_type)
      {
      case Lang::BOOL:
        printf(m_attr==KEYWORD_TRUE?":true":":false");
        break;

      case Lang::INT:
        printf(":%d",getIntegerConst(m_attr));
        break;

      case Lang::FLOAT:
        printf(":%g",getFloatingConst(m_attr));
        break;

      case Lang::VECTOR:
        printf(":%s","TODO");
        break;
      }
    }
    else
      printf(":%s",getIdentifier(m_attr));
  }

  if(m_class==LABEL)
    printf(":%s",getIdentifier(m_attr));
}

bool COperand::isConst() const
{
  assert(m_class==VALUE);
  return m_flavor==CONST;
}

bool COperand::isFloat() const
{
  assert(m_class==VALUE);
  return m_type==Lang::FLOAT;
}

COperand COperand::composeSupertype(const COperand& a,const COperand& b)
{
  assert(a.m_class==VALUE && b.m_class==VALUE);
  
  bool isConst=a.isConst() && b.isConst();
  bool isFloat=a.isFloat() || b.isFloat();

  return COperand(isFloat?Lang::FLOAT:Lang::INT,isConst?CONST:RVALUE,0);
}

int COperand::getAsInteger() const
{
  if(m_class!=VALUE || m_flavor!=CONST)
    throw "invalid conversion attempt";

  switch(m_type)
  {
  case Lang::BOOL:
    return m_attr==KEYWORD_TRUE?1:0;

  case Lang::INT:
    return getIntegerConst(m_attr);

  default:
    throw "incompatible types";
  }
}

float COperand::getAsFloat() const
{
  if(m_class!=VALUE || m_flavor!=CONST)
    throw "invalid conversion attempt";

  switch(m_type)
  {
  case Lang::FLOAT:
    return getFloatingConst(m_attr);
    
  default:
    return getAsInteger();
  }
}

COperand COperand::makeVariable(int attr)
{
  const CSymbol& s=getFrame().getSymbol(attr);
  if(s.m_type>CSymbol::VECTOR)
    throw "unacceptable symbol type";

  return COperand((Lang::Type)(Lang::BOOL+(int)s.m_type),LVALUE,attr);
}

void COperand::makeTemp()
{
  assert(m_class==VALUE);

  const CSymbol& s=CSymbol::makeTemp(m_type);
  getFrame().addDefinition(s);

  m_attr=s.m_name;
  m_flavor=RVALUE;
}


//////////////////////////////////////////////////////////////////////////////


void CQuadruple::print() const
{
  static const char * const _sign[]=
  {
    "LVALUE", /* ссылка на область памяти */
    "RVALUE", /* значение выражения */
    "MUL",
    "DIV",
    "ADD",
    "SUB",
    "ASSIGN",
  };

  static_assert(COUNT(_sign)==MAX_OPERATION);

  printf("<%s",_sign[m_operation]);
  for(int i=0;i<3;++i)
  {
    printf(",");
    m_op[i].print();
  }
  printf(">");
}

CQuadruple CQuadruple::makeArithmetic(int op1, int operation, int op2)
{
  const CQuadruple& a=getQuadruple(op1),&b=getQuadruple(op2);

  COperand r=COperand::composeSupertype(a.m_op[2],b.m_op[2]);

  if(r.isConst())
  {
    int attr=-1;
    if(r.isFloat())
    {
      float va=a.m_op[2].getAsFloat();
      float vb=b.m_op[2].getAsFloat();

      switch(operation)
      {
      case OPERATOR_MUL:
        attr=floatingConst(va*vb);
        break;

      case OPERATOR_DIV:
        attr=floatingConst(va/vb);
        break;

      case OPERATOR_PLUS:
        attr=floatingConst(va+vb);
        break;

      case OPERATOR_MINUS:
        attr=floatingConst(va-vb);
        break;

      default:
        throw "unacceptable operator";
      }
    }
    else
    {
      int va=a.m_op[2].getAsInteger();
      int vb=b.m_op[2].getAsInteger();

      switch(operation)
      {
      case OPERATOR_MUL:
        attr=integerConst(va*vb);
        break;

      case OPERATOR_DIV:
        attr=integerConst(va/vb);
        break;

      case OPERATOR_PLUS:
        attr=integerConst(va+vb);
        break;

      case OPERATOR_MINUS:
        attr=integerConst(va-vb);
        break;

      default:
        throw "unacceptable operator";
      }
    }

    assert(attr!=-1);
    r.m_attr=attr;
    return CQuadruple(RVALUE,COperand(),COperand(),r);
  }
  else
  {
    r.makeTemp();

    switch(operation)
    {
    case OPERATOR_MUL:
      return CQuadruple(MUL,a.m_op[2],b.m_op[2],r);

    case OPERATOR_DIV:
      return CQuadruple(DIV,a.m_op[2],b.m_op[2],r);

    case OPERATOR_PLUS:
      return CQuadruple(ADD,a.m_op[2],b.m_op[2],r);

    case OPERATOR_MINUS:
      return CQuadruple(SUB,a.m_op[2],b.m_op[2],r);

    default:
      throw "unacceptable operator";
    }
  }
}

CQuadruple CQuadruple::makeArithmetic(int operation, int op2)
{
  int op1=pushQuadruple(makeIntegerConst(integerConst(0)));
  return makeArithmetic(op1,operation,op2);
}

CQuadruple CQuadruple::makeLvalue(const COperand& op)
{
  if(op.isConst())
    throw "const used as lvalue";
  return CQuadruple(LVALUE,COperand(),COperand(),op);
}

CQuadruple CQuadruple::makeLvalue(const COperand& op1,int op2)
{
  throw "not implemented";
}

CQuadruple CQuadruple::makeRvalue(const COperand& op)
{
  return CQuadruple(RVALUE,COperand(),COperand(),op);
}

CQuadruple CQuadruple::makeRvalue(int op)
{
  return makeRvalue(getQuadruple(op).m_op[2]);
}

CQuadruple CQuadruple::makeAssignment(int op1,int operation,int op2)
{
  const CQuadruple& a=getQuadruple(op1),&b=getQuadruple(op2);

  if(a.m_operation!=LVALUE)
    throw "rvalue used in assignment";

  if(operation==OPERATOR_ASSIGN)
  {
    return CQuadruple(ASSIGN,a.m_op[2],b.m_op[2],a.m_op[2]);
  }
  else
  {
    throw "not implemented";
    //return makeArithmetic(op1,,op2);
  }
}

